/*-------------------------------------------------------------------------
 - Copyright (c) 2024-2025 [XD-AMCC TEAM]
 - [XD-AMCC] is licensed under Mulan PSL v2.
 - You can use this software according to the terms and conditions of the Mulan PSL v2.
 - You may obtain a copy of Mulan PSL v2 at:
 -          http://license.coscl.org.cn/MulanPSL2
 - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
 - OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
 - TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 - See the Mulan PSL v2 for more details.
 ------------------------------------------------------------------------*/


/**
 * @brief Implement the second lexer KERNEL in Recursive-Descent style.
 *
 *
 * @author  WXQ#XDU
 * @date    2024.12     **** UN-FINISHED, and with bugs ****
 */

#include "_lex_inner.h"

static t_word_info recognize_id_(t_word_info  wordInfo);
static t_word_info recognize_char_(t_word_info  wordInfo);
static t_word_info recognize_cstring_(t_word_info  wordInfo);
static t_word_info recognize_number_(t_word_info  wordInfo, char firstChar);


t_word_info recursive_descent_recognize(t_word_info  wordInfo) {
    EnumTokenKind tokenKind = TK_ERROR;
    int  len = 0;
    char ch1 = READ_CHAR(wordInfo.theLexer);

    switch ( ch1 ) {
    // Part 1: single-char tokens
        case '{':   ++len;  tokenKind = TK_LBRACE;      goto LBL_RETURN;
        case '}':   ++len;  tokenKind = TK_RBRACE;      goto LBL_RETURN;
        case '[':   ++len;  tokenKind = TK_LBRACKET;    goto LBL_RETURN;
        case ']':   ++len;  tokenKind = TK_RBRACKET;    goto LBL_RETURN;
        case '(':   ++len;  tokenKind = TK_LPAREN;      goto LBL_RETURN;
        case ')':   ++len;  tokenKind = TK_RPAREN;      goto LBL_RETURN;
        case ',':   ++len;  tokenKind = TK_COMMA;       goto LBL_RETURN;
        case ':':   ++len;  tokenKind = TK_COLON;       goto LBL_RETURN;
        case ';':   ++len;  tokenKind = TK_SEMI;        goto LBL_RETURN;

    // Part 2: other single-char or double-char tokens
        case '<':   ++len;
            ch1 = READ_CHAR(wordInfo.theLexer);
            if ( '=' == ch1 ) {    // <=
                ++ len;                         tokenKind = TK_OP_LE;
            } else {
                BACK_CHAR(wordInfo.theLexer);   tokenKind = TK_OP_LT;
            }
            goto LBL_RETURN;
        case '>':   ++len;
            ch1 = READ_CHAR(wordInfo.theLexer);
            if ( '=' == ch1 ) {      // >=
                ++ len;                         tokenKind = TK_OP_GE;
            } else {
                BACK_CHAR(wordInfo.theLexer);   tokenKind = TK_OP_GT;
            }
            goto LBL_RETURN;
        case '=':   ++len;
            ch1 = READ_CHAR(wordInfo.theLexer);
            if ( '=' == ch1 ) {      // ==
                ++ len;                         tokenKind = TK_OP_EQ;
            } else {
                BACK_CHAR(wordInfo.theLexer);   tokenKind = TK_OP_ASSIGN;
            }
            goto LBL_RETURN;

        // ...   ...

        //  other  operators

        // ...   ...

        // Part 3: complex tokens
        default:
            // Part 3.1: read ID
            if ( ('_' == ch1) || ('a' <= ch1 && ch1 <= 'z')
                || ('A' <= ch1 && ch1 <= 'Z') )
            {
                wordInfo.n_scanedchars = 1;
                wordInfo.kind = TK_ID;
                wordInfo = recognize_id_(wordInfo);
                return wordInfo;
            }
            // Part 3.2: read INT or REAL literal
            else if ( '0' <= ch1 && ch1 <= '9' ) {
                wordInfo.n_scanedchars = 1;
                wordInfo.kind = TK_INT_LITERAL;
                wordInfo = recognize_number_(wordInfo, ch1);
                return wordInfo;
            }
            // Part 3.3: read a C-string literal
            else if ( '"' == ch1 ) {    // open flag of a C-string literal
                wordInfo.n_scanedchars = 1;
                wordInfo.kind = TK_STRING_LITERAL;
                wordInfo = recognize_cstring_(wordInfo);
                return wordInfo;
            }
            // Part 3.4: read a char literal
            else if ( '\'' == ch1 ) {   // open flag of a character literal
                wordInfo.n_scanedchars = 1;
                wordInfo.kind = TK_CHAR_LITERAL;
                wordInfo = recognize_char_(wordInfo);
                return wordInfo;
            }
            // Part 3.5: bad input
            else {
                len = 0;
                BACK_CHAR(wordInfo.theLexer);
                tokenKind = TK_ERROR;
                goto LBL_RETURN;
            }
    }



LBL_RETURN:
    wordInfo.n_scanedchars = len;
    wordInfo.kind = tokenKind;
    return wordInfo;
}


// read an ID-like word
static t_word_info recognize_id_(t_word_info  wordInfo) {          // FINISHED

    char ch1 = READ_CHAR(wordInfo.theLexer);
    while (  ('_' == ch1)
             || ('a' <= ch1 && ch1 <= 'z')
             || ('A' <= ch1 && ch1 <= 'Z')
             || ('0' <= ch1 && ch1 <= '9') )
    {
        ++ (wordInfo.n_scanedchars);
        ch1 = READ_CHAR(wordInfo.theLexer);
    }

    BACK_CHAR(wordInfo.theLexer);
    return  wordInfo;
}



// read a character literal following the open tag
//   - it may be a normal char, or an escaped sequence
//   - and the close tag, "'"
static t_word_info recognize_char_(t_word_info  wordInfo) {        // FINISHED

    int  nEscapedChar = 0;
    char ch1 = READ_CHAR(wordInfo.theLexer);
    // the closing flag, End-Of-Line, End-Of-File
    if ( ('\'' == ch1) || IS_EOL(ch1) || IS_EOF(ch1) ) {
        goto LBL_ERROR_ABORT;
    }

    ++ (wordInfo.n_scanedchars);

    if ( '\\' == ch1 ) {             // escaped sequence of a char
        ch1 = READ_CHAR(wordInfo.theLexer);
        switch ( ch1 ) {
            case '\'':   case '?':    case '"':  case 'a':   case 'b':
            case 'f' :   case 'n':    case 'r':  case 'v':    case 't':
            case '\\' :
                ++ (wordInfo.n_scanedchars);
                break;
            case 'x' : case 'X' : // following one or tow HEX char
                ++ (wordInfo.n_scanedchars);
                for (nEscapedChar = 0; nEscapedChar < 2; ++nEscapedChar) {
                    ch1 = READ_CHAR(wordInfo.theLexer);
                    if (   ('0' <= ch1 && ch1 <= '9')
                           || ('a' <= ch1 && ch1 <= 'f')
                           || ('A' <= ch1 && ch1 <= 'F') )
                    {
                        ++ (wordInfo.n_scanedchars);
                    } else {
                        break;
                    }
                }
                switch (nEscapedChar) {
                    case 1:  BACK_CHAR(wordInfo.theLexer); break;
                    case 2:  break;
                    default: goto LBL_ERROR_ABORT;
                }
                break;
            case '0':  case '1':  case '2':  case '3':
            case '4':  case '5':  case '6':  case '7':  // following 1~3 OCT char
                ++ (wordInfo.n_scanedchars);
                for (nEscapedChar = 1; nEscapedChar < 3; ++nEscapedChar) {
                    ch1 = READ_CHAR(wordInfo.theLexer);
                    if ( '0' <= ch1 && ch1 <= '7' )
                        ++ (wordInfo.n_scanedchars);
                    else
                        break;
                }
                switch (nEscapedChar) {
                    case 1:  case 2: BACK_CHAR(wordInfo.theLexer); break;
                    case 3:  break;
                    default: goto LBL_ERROR_ABORT;
                }
                break;
            default:  // unknown escaped sequence
                goto LBL_ERROR_ABORT;
        }
    }

    // the closing flag : "'"
    ch1 = READ_CHAR(wordInfo.theLexer);
    if ( '\'' == ch1 ) {
        ++ (wordInfo.n_scanedchars);
        wordInfo.kind = TK_CHAR_LITERAL;
    } else {
        goto LBL_ERROR_ABORT;
    }

    return  wordInfo;

LBL_ERROR_ABORT:
    wordInfo.kind = TK_ERROR;
    BACK_CHAR(wordInfo.theLexer);
    return  wordInfo;
}


// read a REAL number, INT number with a specified base(radix)
//    TK_INT_LITERAL_BIN    // 0 [bB] (0|1)+
//    TK_INT_LITERAL_HEX    // 0 [xX] [0-9a-fA-F]+
//    TK_INT_LITERAL_OCT    // 0 [0-7]*
//    TK_INT_LITERAL_DEC    // [1-9] [0-9]*
//
//    TK_REAL_LITERAL       // [0-9]+ ( . [0-9]+ )?  ( [eE] [-+] [0-9]+ )?
//
static t_word_info recognize_number_(t_word_info  wordInfo, char firstChar) {

//  -->  Please the reader, you, implement this func :-)

//    char ch1 = READ_CHAR(wordInfo.theLexer);

    //................

//    BACK_CHAR(wordInfo.theLexer);

    return  wordInfo;
}



// read a C-string literal following the open tag
//   - each char may be a normal char, or an escaped sequence
//   - and the close tag, '"'
static t_word_info recognize_cstring_(t_word_info  wordInfo) {

// -->  Please the reader, you, implement this func :-)

//    char ch1 = READ_CHAR(wordInfo.theLexer);

    //................

//    BACK_CHAR(wordInfo.theLexer);

    return  wordInfo;
}
